/*
 * This file is part of TradeHelper library.
 *
 * TradeHelper library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * TradeHelper library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with TradeHelper library.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.rag.managers;

import com.alee.extended.window.WebProgressDialog;
import com.alee.laf.WebLookAndFeel;
import com.alee.managers.settings.SettingsManager;
import com.alee.utils.FileUtils;
import com.alee.utils.XmlUtils;
import com.alee.utils.file.FileDownloadListener;
import com.alee.utils.swing.Timer;
import com.rag.core.*;
import com.rag.managers.update.NewsListener;
import com.rag.managers.update.UpdateListener;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
 * User: mgarin Date: 11.04.12 Time: 17:51
 */

public class UpdateManager
{
    public static final String updateAddress = "http://weblookandfeel.com/downloads/motr/";
    public static final String announceFile = updateAddress + "announce.xml";
    public static final String newsFile = updateAddress + "news.xml";
    public static final String updatesFile = updateAddress + "updates.xml";
    public static final String dataFile = updateAddress + "data.zip";
    public static final String synonymsFile = updateAddress + "synonyms.xml";
    public static final String updateExe = updateAddress + "TradeHelper.exe";
    public static final String updateJar = updateAddress + "TradeHelper.jar";
    public static final String updateData = updateAddress + "data.xml";

    public static final String localUpdatesFile = "updates.xml";
    public static final String localDataFile = "data.xml";
    public static final String localSynonymsFile = "synonyms.xml";

    private static final String keyPrefix = "UpdateManager.";

    private static List<NewsListener> listeners = new ArrayList<NewsListener> ();

    private static NewsData announceData = null;
    private static NewsData newsData = null;

    /**
     * Загруженные ранее данные
     */

    public static NewsData getAnnounceData ()
    {
        return announceData;
    }

    public static NewsData getNewsData ()
    {
        return newsData;
    }

    /**
     * Инициализируем update-менеджера
     */

    private static boolean initialized = false;

    public static void initialize ( UpdateListener updateListener )
    {
        if ( !initialized )
        {
            initialized = true;

            // Проверка обновлений данных при инициализации
            checkForUpdates ( updateListener );
        }
    }

    /**
     * Загрузка последней анонсов
     */

    public static boolean loadAnnounce ()
    {
        try
        {
            // Загружаем новости
            announceData = ( NewsData ) XmlUtils.fromXML ( new URL ( announceFile ) );

            // Проверяем на наличие свежих обновлений
            if ( announceData != null )
            {
                long latest = 0;
                SingleNews latestAnnounce = null;
                for ( SingleNews singleNews : announceData.getNews () )
                {
                    if ( singleNews.getDate () > latest )
                    {
                        latest = singleNews.getDate ();
                        latestAnnounce = singleNews;
                    }
                }
                if ( latestAnnounce != null )
                {
                    Long ld = latestAnnounce.getDate ();
                    if ( ld < SettingsManager.getLong ( keyPrefix + "lastAnnounceLoad", 0L ) )
                    {
                        latestAnnounce = null;
                    }
                    SettingsManager.set ( keyPrefix + "lastAnnounceLoad", ld + 1 );
                }
                if ( latestAnnounce != null )
                {
                    fireAnnounceUpdated ( latestAnnounce );
                }
                return latestAnnounce != null;
            }
            else
            {
                return false;
            }
        }
        catch ( Throwable e )
        {
            return false;
        }
    }

    /**
     * Загрузка последних новостей
     */

    public static boolean loadNews ()
    {
        try
        {
            // Загружаем новости
            newsData = ( NewsData ) XmlUtils.fromXML ( new URL ( newsFile ) );

            // Проверяем на наличие свежих обновлений
            if ( newsData != null )
            {
                long latest = 0;
                SingleNews latestNews = null;
                for ( SingleNews singleNews : newsData.getNews () )
                {
                    if ( singleNews.getDate () > latest )
                    {
                        latest = singleNews.getDate ();
                        latestNews = singleNews;
                    }
                }
                if ( latestNews != null )
                {
                    Long ld = latestNews.getDate ();
                    if ( ld < SettingsManager.getLong ( keyPrefix + "lastNewsLoad", 0L ) )
                    {
                        latestNews = null;
                    }
                    SettingsManager.set ( keyPrefix + "lastNewsLoad", ld + 1 );
                }
                if ( latestNews != null )
                {
                    fireNewsUpdated ( latestNews );
                }
                return latestNews != null;
            }
            else
            {
                return false;
            }
        }
        catch ( MalformedURLException e )
        {
            e.printStackTrace ();
            return false;
        }
        catch ( IOException e )
        {
            e.printStackTrace ();
            return false;
        }
    }

    /**
     * Слушатели обновления новостей
     */

    public static void addNewsListener ( NewsListener listener )
    {
        listeners.add ( listener );
    }

    public static void removeNewsListener ( NewsListener listener )
    {
        listeners.remove ( listener );
    }

    private static void fireAnnounceUpdated ( SingleNews announceNews )
    {
        List<NewsListener> listenersCopy = new ArrayList<NewsListener> ();
        listenersCopy.addAll ( listeners );
        for ( NewsListener listener : listenersCopy )
        {
            listener.announceUpdated ( announceNews );
        }
    }

    private static void fireNewsUpdated ( SingleNews updatedNews )
    {
        List<NewsListener> listenersCopy = new ArrayList<NewsListener> ();
        listenersCopy.addAll ( listeners );
        for ( NewsListener listener : listenersCopy )
        {
            listener.newsUpdated ( updatedNews );
        }
    }

    /**
     * Загрузка обновлений данных, используемых приложением
     */

    public static void checkForUpdates ( final UpdateListener updateListener )
    {
        // Показываем диалог проверки обновлений
        final WebProgressDialog progressDialog = new WebProgressDialog ( "Загрузка" );
        progressDialog.setIconImages ( WebLookAndFeel.getImages () );
        progressDialog.setText ( "Инициализация приложения..." );
        progressDialog.setIndeterminate ( true );
        progressDialog.setShowProgressText ( false );
        progressDialog.setDefaultCloseOperation ( WebProgressDialog.DO_NOTHING_ON_CLOSE );
        progressDialog.setPreferredWidth ( 300 );
        progressDialog.setModal ( false );
        progressDialog.setVisible ( true );

        // Грузим информацию об обновлениях и обновления в отдельном потоке
        new Thread ( new Runnable ()
        {
            public void run ()
            {
                // Загружаем даты обновления данных для сверки 
                progressDialog.setText ( "Загрузка информации об обновлениях..." );
                Updates localUpdates = loadLocalUpdates ();
                Updates remoteUpdates = loadRemoteUpdates ();

                // Требуется обновление основных данных
                if ( !localDataExists () || localUpdates.getData () == 0 ||
                        localUpdates.getData () < remoteUpdates.getData () )
                {
                    try
                    {
                        progressDialog.setText ( "Загрузка обновления базы данных..." );
                        RagData ragData = loadRemoteData ( new FileDownloadListener ()
                        {
                            private int total = 0;

                            public void sizeDetermined ( int i )
                            {
                                total = i;
                                progressDialog.setIndeterminate ( false );
                                progressDialog.setShowProgressText ( true );
                                updateProgressText ( i );
                                progressDialog.setMinimum ( 0 );
                                progressDialog.setMaximum ( i );
                            }

                            public void partDownloaded ( int i )
                            {
                                progressDialog.setProgress ( i );
                                updateProgressText ( i );
                            }

                            private void updateProgressText ( int downloaded )
                            {
                                progressDialog.setProgressText (
                                        FileUtils.getFileSizeString ( downloaded ) + " из " +
                                                FileUtils.getFileSizeString ( total ) );
                            }

                            public boolean shouldStopDownload ()
                            {
                                return false;
                            }

                            public void fileDownloaded ( File file )
                            {
                                progressDialog.setIndeterminate ( true );
                                progressDialog.setShowProgressText ( false );
                            }

                            public void fileDownloadFailed ( Throwable throwable )
                            {
                                progressDialog.setIndeterminate ( true );
                                progressDialog.setShowProgressText ( false );
                            }
                        } );
                        SettingsManager.setSettings ( localDataFile, ragData );
                        localUpdates.setData ( remoteUpdates.getData () );
                    }
                    catch ( Throwable e )
                    {
                        e.printStackTrace ();
                    }
                }

                // Требуется обновление синонимов
                if ( !localSynonymsExists () || localUpdates.getSynonyms () == 0 ||
                        localUpdates.getSynonyms () < remoteUpdates.getSynonyms () )
                {
                    try
                    {
                        progressDialog.setText ( "Загрузка обновления базы синонимов..." );
                        SynonymData synonymData = loadRemoteSynonyms ();
                        SettingsManager.setSettings ( localSynonymsFile, synonymData );
                        localUpdates.setSynonyms ( remoteUpdates.getSynonyms () );
                    }
                    catch ( Throwable e )
                    {
                        e.printStackTrace ();
                    }
                }

                // Обновляем локально хранящиеся даты обновления файлов
                SettingsManager.setSettings ( localUpdatesFile, localUpdates );

                // Начинаем таймер проверок новостей
                startNewsCheck ();

                // Уведомляем о завершении проверки обновлений
                progressDialog.setText ( "Загрузка приложения..." );
                updateListener.updateComplete ();

                // Прячем диалог загрузки
                progressDialog.setVisible ( false );
            }
        } ).start ();
    }

    private static Updates loadLocalUpdates ()
    {
        return ( Updates ) SettingsManager.getSettings ( localUpdatesFile, new Updates ( 0, 0 ) );
    }

    private static Updates loadRemoteUpdates ()
    {
        try
        {
            return ( Updates ) XmlUtils.fromXML ( new URL ( updatesFile ) );
        }
        catch ( Throwable e )
        {
            return new Updates ( 0, 0 );
        }
    }

    private static boolean localDataExists ()
    {
        return SettingsManager.settingsExists ( localDataFile );
    }

    private static RagData loadRemoteData ( FileDownloadListener listener ) throws Throwable
    {
        // Скачиваем архив с файлом данных
        File tmp = FileUtils
                .downloadFile ( dataFile, File.createTempFile ( "data", ".zip" ), listener );

        // Читаем файл данных из архива (он должен быть один)
        ZipFile zipFile = new ZipFile ( tmp );
        Enumeration entries = zipFile.entries ();
        ZipEntry entry = ( ZipEntry ) entries.nextElement ();
        RagData ragData = ( RagData ) XmlUtils.fromXML ( zipFile.getInputStream ( entry ) );
        zipFile.close ();

        // Удаляем временный локальный файл
        FileUtils.removeFile ( tmp );

        // Кэшируем загруженные данные
        SettingsManager.setSettings ( localDataFile, ragData );

        // Возвращаем результат
        return ragData;
    }

    private static boolean localSynonymsExists ()
    {
        return SettingsManager.settingsExists ( localDataFile );
    }

    private static SynonymData loadRemoteSynonyms () throws Throwable
    {
        // Загружаем данные о синонимах
        SynonymData synonymData = ( SynonymData ) XmlUtils.fromXML ( new URL ( synonymsFile ) );

        // Кэшируем загруженные данные
        SettingsManager.setSettings ( localSynonymsFile, synonymData );

        // Возвращаем результат
        return synonymData;
    }

    private static void startNewsCheck ()
    {
        // Тред проверки обновлений новостей (раз в 10 минут)
        new Timer ( 10 * 60 * 1000, 15000, new ActionListener ()
        {
            public void actionPerformed ( ActionEvent e )
            {
                loadAnnounce ();
                loadNews ();
            }
        } ).start ();
    }
}